package w.z.x.framework.repository.structures;

import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import w.z.x.framework.repository.annotations.Mapper;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

/**
 * java bean
 *
 * @author 魏祖潇
 */
@Slf4j
@Data
public class BeanHelper implements Serializable {
    private static final long serialVersionUID = -1L;
    private final static Pattern CAMEL_CASE_PATTERN = Pattern.compile("([A-Za-z\\d]+)(_)?");
    private final static Pattern UNDERLINE_PATTERN = Pattern.compile("[A-Z]([a-z\\d]+)?");

    private Mapper mapper;
    private Class clazz;
    private BeanHelper parent;
    private Collection<Class> interfaces;
    private Collection<Field> fields;
    private Collection<Method> methods;

    /**
     * 构造函数
     *
     * @param current 当前类
     */
    public BeanHelper(final Class current) {
        this.mapper = getMapper(current);
        this.clazz = current;
        this.parent = getParent(current);
        this.interfaces = Arrays.asList(current.getInterfaces());
        this.fields = getFields(this);
        this.methods = Arrays.asList(current.getMethods());
    }

    /**
     * 获取注解
     *
     * @param clazz 类型
     * @return 注解
     */
    private static Mapper getMapper(Class clazz) {
        if (!clazz.isAnnotationPresent(Mapper.class)) {
            return null;
        }
        Mapper m = (Mapper) clazz.getAnnotation(Mapper.class);
        return m;
    }

    /**
     * 获取父类
     *
     * @param clazz 当前类
     * @return 父类
     */
    private static BeanHelper getParent(Class clazz) {
        Class parent = clazz.getSuperclass();
        if (Objects.isNull(parent)) {
            return null;
        }
        BeanHelper helper = new BeanHelper(parent);
        return helper;
    }

    /**
     * 获取所有字段，含继承的
     *
     * @param clazz 当前类
     * @return 字段
     */
    private static Collection<Field> getFields(BeanHelper clazz) {
        final BeanHelper[] curr = {clazz};
        Collection<Field> fs = new ArrayList<>();
        while (Objects.nonNull(curr[0].parent)) {
            fs.addAll(Arrays.asList(curr[0].clazz.getDeclaredFields()));
            curr[0] = curr[0].parent;
        }
        return fs;
    }

    /**
     * 首字母转大写
     *
     * @param fieldName 字段名称
     * @return 首字母大写
     */
    public static String toFirstUpperCase(String fieldName) {
        byte[] items = fieldName.getBytes();
        items[0] = (byte) ((char) items[0] - 'a' + 'A');
        return new String(items);
    }

    /**
     * 转驼峰格式
     *
     * @param fieldName 字段
     * @return 驼峰格式
     */
    public static String toCamelCase(String fieldName) {
        return StrUtil.toCamelCase(fieldName);
    }

    /**
     * 转下划线格式
     *
     * @param fieldName 字段
     * @return 下划线格式
     */
    public static String toUnderline(String fieldName) {
        return StrUtil.toUnderlineCase(fieldName);
    }

    /**
     * 获取有效列和值
     *
     * @param obj 对象
     * @return 列
     */
    public Map<Field, Object> columns(Object obj) {
        if (Objects.isNull(obj)) {
            return Collections.EMPTY_MAP;
        }
        if (!obj.getClass().equals(clazz)) {
            return Collections.EMPTY_MAP;
        }
        LinkedHashMap<Field, Object> map = new LinkedHashMap<>();
        fields.stream().forEach(f -> {
            if (Objects.isNull(f)) {
                return;
            }
            Optional<Method> opt = methods.stream()
                    .filter(m -> m.getName().equals("get" + toFirstUpperCase(f.getName())) || m.getName().equals("is" + toFirstUpperCase(f.getName())))
                    .findFirst();
            if (!opt.isPresent()) {
                return;
            }
            Object value;
            try {
                opt.get().setAccessible(true);
                value = opt.get().invoke(obj);
            } catch (Exception e) {
                log.error("getObjectValue error => ", e);
                return;
            }
            if (Objects.isNull(value)) {
                return;
            }
            map.put(f, value);
        });
        return map;
    }

}
